#include "MainWindow.h"
#include <QtAlgorithms>
#include "vmidi_rtmidi.h"

using namespace VMidi;


/**
  * Callbacks.
  */

void MainWindow::on_tfPortName_textChanged (const QString& portName) {
    bool activate = !portName.isEmpty();
    btnCreateInput->setEnabled(activate);
    btnCreateOutput->setEnabled(activate);
}

// Connect/Disconnect ports.
void MainWindow::on_tblMatrix_cellClicked(int row, int col) {
    QTableWidgetItem* item  = tblMatrix->item(row, col);

    if (item != NULL) {
        if (item->checkState() == Qt::Checked) {
            coreConnect(col, row);
        }
        else if (item->checkState() == Qt::Unchecked) {
           coreDisconnect(col, row);
        }
    }
}

void MainWindow::on_tblMatrix_cellChanged(int row, int col) {
    QString inputName = tblMatrix->horizontalHeaderItem(col)->text();
    QString outputName = tblMatrix->verticalHeaderItem(row)->text();

    lblInformation->setText("VMidi THRU connection: "+inputName+" >> "+outputName);
}

void MainWindow::on_btnCreateInput_clicked() {
    // Port identifier in the table, used to associate with the core id.
    int portIdTable = -1;

    QString portName = mkPortName(VMIDI_PORT_INPUT);

    /**
      * GUI
      */
    QTableWidgetItem* header = NULL;
    {
        int col = tblMatrix->columnCount();
        portIdTable = col;
        tblMatrix->insertColumn(col);

        header = new QTableWidgetItem(portName);
        tblMatrix->setHorizontalHeaderItem(col, header);

        // Create the checkable cells.
        for (int r = 0; r < tblMatrix->rowCount(); ++r) {
            QTableWidgetItem* item = new QTableWidgetItem();
            item->setCheckState(Qt::Unchecked);
            tblMatrix->setItem(r, col, item);
            QWidget* cellWidget = tblMatrix->cellWidget(r, col);
            if (cellWidget != NULL) {
                cellWidget->installEventFilter(this);
            }
        }

        updateToolboxState();
    }

    /**
      * Core
      */
    // Core port identifier.
    int portId = -1;
    {
        vmidiCore->createVirtualPort(VMIDI_PORT_INPUT, portName.toStdString(), portId);
    }

    header->setData(Qt::UserRole, QVariant(portId));
}

void MainWindow::on_btnCreateOutput_clicked() {
    // Port identifier in the table, used to associate with the core id.
    int portIdTable = -1;

    QString portName = mkPortName(VMIDI_PORT_OUTPUT);

    /**
      * GUI
      */
    QTableWidgetItem* header = NULL;
    {
        int row = tblMatrix->rowCount();
        portIdTable = row;
        tblMatrix->insertRow(row);

        header = new QTableWidgetItem(portName);
        tblMatrix->setVerticalHeaderItem(row, header);

        // Create the checkable cells.
        for (int c = 0; c < tblMatrix->columnCount(); ++c) {
            QTableWidgetItem* item = new QTableWidgetItem();
            item->setCheckState(Qt::Unchecked);
            tblMatrix->setItem(row, c, item);
            QWidget* cellWidget = tblMatrix->cellWidget(row, c);
            if (cellWidget != NULL) {
                cellWidget->installEventFilter(this);
            }
        }

        updateToolboxState();
    }

    /**
      * Core
      */
    // Core port identifier.
    int portId = -1;
    {
        vmidiCore->createVirtualPort(VMIDI_PORT_OUTPUT, portName.toStdString(), portId);
    }

    header->setData(Qt::UserRole, QVariant(portId));
}

void MainWindow::on_btnDeleteInput_clicked() {
    QModelIndexList items = tblMatrix->selectionModel()->selectedIndexes();
    QSet<int> rows;
    QSet<int> cols;

    findSelectedRowsAndColumns(rows,cols,items);
    QList<int> colsL = cols.toList();
    qSort(colsL.begin(), colsL.end(), qGreater<int>());

    // TODO : confirmation popup with the list of ports to be destroyed.
    // if (confirm destroy) {


    for (QList<int>::iterator itr = colsL.begin(); itr != colsL.end(); ++itr) {
        int portIdTable = *itr;
        int portCoreId = getCoreId(portIdTable, VMIDI_PORT_INPUT);

        // GUI
        {
            tblMatrix->removeColumn(portIdTable);
        }
        // Core
        {
            try {
                vmidiCore->destroyVirtualPort(VMIDI_PORT_INPUT, portCoreId);
            }
            catch (string& s) {
                // Note that it's not necessary an error happening here !
                cerr << s << endl;
            }
        }
    }

    updateToolboxState();

    // } end popup confirmation
}

void MainWindow::on_btnDeleteOutput_clicked() {
    QModelIndexList items = tblMatrix->selectionModel()->selectedIndexes();
    QSet<int> rows;
    QSet<int> cols;

    findSelectedRowsAndColumns(rows,cols,items);
    QList<int> rowsL = rows.toList();
    qSort(rowsL.begin(), rowsL.end(), qGreater<int>());

    // TODO : confirmation popup with the list of ports to be destroyed.
    // if (confirm destroy) {


    for (QList<int>::iterator itr = rowsL.begin(); itr != rowsL.end(); ++itr) {
        int portIdTable = *itr;
        int portCoreId = getCoreId(portIdTable, VMIDI_PORT_OUTPUT);

        // GUI
        {
            tblMatrix->removeRow(portIdTable);
        }
        // Core
        {
            try {
                vmidiCore->destroyVirtualPort(VMIDI_PORT_OUTPUT, portCoreId);
            }
            catch (string& s) {
                // Note that it's not necessary an error happening here !
                cerr << s << endl;
            }
        }
    }

    updateToolboxState();

    // } end popup confirmation
}


void MainWindow::on_btnClearAllMatrix_clicked() {
    for (int r = 0; r < tblMatrix->rowCount(); ++r) {
        for (int c = 0; c < tblMatrix->columnCount(); ++c) {
            QTableWidgetItem* item = tblMatrix->item(r, c);
            // Only clear checked cells.
            if ((item != NULL) && (item->checkState() == Qt::Checked)) {
                item->setSelected(false);
                item->setCheckState(Qt::Unchecked);
                coreDisconnect(c,r);
            }
        }
    }
}

void MainWindow::on_btnClearInputMatrix_clicked() {
    QModelIndexList items = tblMatrix->selectionModel()->selectedIndexes();
    QSet<int> rows;
    QSet<int> cols;

    findSelectedRowsAndColumns(rows,cols,items);

    for (int r = 0; r < tblMatrix->rowCount(); ++r) {
        for (QSet<int>::iterator it = cols.begin(); it != cols.end(); ++it) {
            QTableWidgetItem* item = tblMatrix->item(r, *it);
            // Only clear checked cells.
            if ((item != NULL) && (item->checkState() == Qt::Checked)) {
                item->setSelected(false);
                item->setCheckState(Qt::Unchecked);
                coreDisconnect(*it,r);
            }
        }
    }
}

void MainWindow::on_btnClearOutputMatrix_clicked() {
    QModelIndexList items = tblMatrix->selectionModel()->selectedIndexes();
    QSet<int> rows;
    QSet<int> cols;

    findSelectedRowsAndColumns(rows,cols,items);

    for (QSet<int>::iterator it = rows.begin(); it != rows.end(); ++it) {
        for (int c = 0; c < tblMatrix->columnCount(); ++c) {
            QTableWidgetItem* item = tblMatrix->item(*it, c);
            // Only clear checked cells.
            if ((item != NULL) && (item->checkState() == Qt::Checked)) {
                item->setSelected(false);
                item->setCheckState(Qt::Unchecked);
                coreDisconnect(c,*it);
            }
        }
    }
}

void MainWindow::on_btnBroadcastMatrix_clicked() {
    int input = tblMatrix->currentColumn();

    if (input < 0) {
        return;
    }

    for (int r = 0; r < tblMatrix->rowCount(); ++r) {
        QTableWidgetItem* item = tblMatrix->item(r, input);
        if (item != NULL) {
            tblMatrix->item(r, input)->setSelected(true);
            tblMatrix->item(r, input)->setCheckState(Qt::Checked);
            coreConnect(input, r);
        }
    }
}

void MainWindow::on_btnDiagonalMatrix_clicked() {
    for (int n = 0; n < tblMatrix->rowCount(); ++n) {
        QTableWidgetItem* item = tblMatrix->item(n, n);
        if (item != NULL) {
            tblMatrix->item(n, n)->setSelected(true);
            tblMatrix->item(n, n)->setCheckState(Qt::Checked);
            coreConnect(n, n);
        }
    }
}

void MainWindow::on_btnToggleActivation_clicked() {
    active ^= true;
    toggleActivation(active);
}

/**
  * Internal methods.
  */


void MainWindow::connectWidgets() {
    // EMPTY.
}

void MainWindow::initCore () {
    vmidiCore = VMidiFactory::getInstance();
    vmidiCore->initialize();
}

void MainWindow::finalizeCore() {
    vmidiCore->finalize();
}

QString MainWindow::mkPortName (VMidiPortType type) {
    // No prefix : simply set the name as it is.
    if (!cbUsePrefix->isChecked()) {
        return tfPortName->text();
    }
    else {
        // Prefix to search for.
        QString prefix = tfPortName->text();

        // How many elements to scan for ?
        int nelements = (type == VMIDI_PORT_INPUT) ?
                    tblMatrix->columnCount() :
                    tblMatrix->rowCount();

        QSet<int> sequence;
        int maxNumber = -1;

        // Scan for all the ports of the specified kind and find the next available number.
        for (int k = 0; k < nelements; ++k) {
            QTableWidgetItem* header = (type == VMIDI_PORT_INPUT) ?
                        tblMatrix->horizontalHeaderItem(k) :
                        tblMatrix->verticalHeaderItem(k);

            QString portName = header->text();
            int index = -1;

            if ((index = portName.indexOf(prefix)) == -1) {
                continue;
            }

            QString numberS = portName.mid(prefix.length());
            int number = numberS.toInt();

            cout << "Number inserted : " << number << endl;
            sequence.insert(number);

            if (number > maxNumber) {
                maxNumber = number;
            }
        }

        // -1 is used as a NDV to know that no number has been found.
        int nextAvailableNumber = -1;

        for (int number = 1; number <= maxNumber; ++number) {
            // Number not found : this is our next available number.
            if ((sequence.find(number)) == sequence.end()) {
                nextAvailableNumber = number;
                break;
            }
        }

        // No hole found in the sequence so take max + 1 if there was an element, else set 1.
        if (nextAvailableNumber < 0) {
            if (maxNumber > 0) {
                nextAvailableNumber = maxNumber + 1;
            }
            else {
                nextAvailableNumber = 1;
            }
        }

        QString portName = prefix.append(QString::number(nextAvailableNumber));
        cout << "Port name : " << portName.toStdString() << endl;

        return portName;
    }
}

/**
  * Connect 2 ports.
  */
void MainWindow::coreConnect (int portIdIN, int portIdOUT) {
    int inPortIdCore = getCoreId(portIdIN, VMIDI_PORT_INPUT);
    int outPortIdCore =  getCoreId(portIdOUT, VMIDI_PORT_OUTPUT);

    vmidiCore->connectPorts(inPortIdCore, outPortIdCore);
}

/**
  * Disconnect 2 ports.
  */
void MainWindow::coreDisconnect (int portIdIN, int portIdOUT) {
    int inPortIdCore = getCoreId(portIdIN, VMIDI_PORT_INPUT);
    int outPortIdCore =  getCoreId(portIdOUT, VMIDI_PORT_OUTPUT);

    // Core system doesn't like multiple disconnections, just like double frees.
    try {
        vmidiCore->disconnectPorts(inPortIdCore, outPortIdCore);
    }
    catch (string& s) {
        // Note that it's not necessary an error happening here !
        cerr << s << endl;
    }
}

void MainWindow::findSelectedRowsAndColumns (QSet<int>& rows, QSet<int>& cols, QModelIndexList& items) {
    for (QModelIndexList::iterator itr = items.begin();
         itr != items.end();
         ++itr) {
        QModelIndex item = *itr;
        rows.insert(item.row());
        cols.insert(item.column());
    }
}


void MainWindow::updateToolboxState() {
    int ninputs = tblMatrix->columnCount();
    int noutputs = tblMatrix->rowCount();

    framToolbox->setEnabled(ninputs*noutputs > 0);
    btnDiagonalMatrix->setEnabled((ninputs == noutputs) && (ninputs > 1));
    btnBroadcastMatrix->setEnabled(noutputs > 1);

    // Avoid trailing headers.
    //    if (ninputs == 0) {
    //        if (noutputs > 0) {
    //            for (int r = 0; r < noutputs; ++r) {
    //                QTableWidgetItem* item = tblMatrix->takeVerticalHeaderItem(r);
    //                delete item;
    //            }
    //        }
    //    }
    //    if (noutputs == 0) {
    //        if (ninputs > 0) {
    //            for (int c = 0; c < ninputs; ++c) {
    //                QTableWidgetItem* item = tblMatrix->takeHorizontalHeaderItem(c);
    //                delete item;
    //            }
    //        }
    //    }
}

// Event filter used to display information on the current focused cell.
bool MainWindow::eventFilter(QObject *object, QEvent *event)
{
    if (event->type() == QEvent::FocusIn) {
        cout << "Focus IN on object " << object << endl;

        QTableWidgetItem* item = dynamic_cast<QTableWidgetItem*>(object);

        if (item != NULL) {
            int row = item->row();
            int col = item->column();
            QString inputName = tblMatrix->horizontalHeaderItem(col)->text();
            QString outputName = tblMatrix->verticalHeaderItem(row)->text();

            if (item->checkState() == Qt::Checked) {
            lblInformation->setText("VMidi THRU connection: "+inputName+" >> "+outputName);
            }
            else {
                lblInformation->setText("VMidi THRU connection: none.");
            }

            // Filtered.
            return true;
        }
    }

    // Not filtered.
    return false;
}

void MainWindow::toggleActivation (bool state) {
    vmidiCore->setActive(state);

    if (state) {
        lblActivationStatus->setText("Current state : active");
    }
    else {
        lblActivationStatus->setText("Current state : inactive");
    }
}

int MainWindow::getCoreId (int index, VMidiPortType type) {
    QTableWidgetItem* item = (type == VMIDI_PORT_INPUT) ?
                (tblMatrix->horizontalHeaderItem(index)) :
                (tblMatrix->verticalHeaderItem(index));

    return item->data(Qt::UserRole).toInt();
}
